#include "ff.h"      // 引入 FatFs 主头文件
#include <stdio.h>   // 用于 printf
#include <string.h>  // 用于 strcmp 和 memset

/**
 ****************************************************************************************************
 * @file      fatfs_example.c
 * @brief     这是一个在嵌入式系统中使用FatFs文件系统库操作外部串行FLASH的示例代码。
 * 主要演示了如何挂载文件系统、格式化、创建文件、写入数据、读取数据和卸载文件系统等基本操作。
 ****************************************************************************************************
 */

#include "ff.h" // 引入FatFs库的头文件
#include "malloc_ex.h"
#include "stdio.h" // 引入标准输入输出库，用于printf打印信息

/* 全局变量定义区 */

FATFS fs;                      // FatFs文件系统对象。可以理解为代表整个FLASH存储设备的文件系统实例。
FIL fnew;                      // 文件对象。可以理解为一个“文件句柄”，后续所有对文件的操作都需要通过它来进行。
FRESULT res_flash;             // 用于存储FatFs函数操作结果的枚举变量。通过检查它的值可以知道操作是否成功。
UINT fnum;                     // 用于记录成功读取或写入的字节数。

// 准备一个缓冲区，用于存放从文件中读取出来的数据
#define FILE_READ_BUF_SIZE 1024
// BYTE fileReadBuffer[1024];
BYTE *fileReadBuffer=NULL;
// 准备一个缓冲区，存放我们想要写入到文件中的数据
#define FILE_WRITE_BUF_SIZE 1024
// BYTE fileWriteBuffer[] = "Hello, FatFs! This is a test message. nice to meet you!";
BYTE *fileWriteBuffer=NULL;
// FatFs工作区。某些操作（如格式化）需要一块内存作为临时工作区。
// 这块内存越大，处理速度通常越快。FF_MAX_SS是FatFs定义的单个扇区的最大字节数。
// BYTE work[FF_MAX_SS];
BYTE *work=NULL;

/**
 * @brief  主程序入口函数
 * @param  None
 * @retval None
 */
void hal_entry(void)
{

    if (fileReadBuffer==NULL)
        fileReadBuffer=(BYTE*)mymalloc(SRAMEX, FILE_READ_BUF_SIZE);
    if (fileWriteBuffer==NULL)
    {
        fileWriteBuffer=(BYTE*)mymalloc(SRAMEX, FILE_WRITE_BUF_SIZE);
        snprintf((char *)fileWriteBuffer,1024,"Hello, FatFs! This is a test message. nice to meet you!");
    }
    if (work==NULL)
        work=(BYTE*)mymalloc(SRAMEX,FF_MAX_SS);
    printf("这是一个串行FLASH的FatFs使用演示例程\r\n");
    printf("请打开串口助手查看打印的调试信息\r\n\r\n");

    // -----------------------------------------------------------------------------------------------
    // 步骤 1: 挂载文件系统
    // "1:" 是FatFs中盘符的表示方式，代表第一个逻辑驱动器。
    // 第三个参数 '1' 表示立即挂载。
    // -----------------------------------------------------------------------------------------------
    res_flash = f_mount(&fs, "1:", 1);

    // 检查挂载结果
    if (res_flash == FR_NO_FILESYSTEM)
    {
        // 如果没有找到文件系统，说明FLASH是全新的或者数据已损坏，需要格式化
        printf(">>> FLASH上未发现文件系统，即将进行格式化...\r\n");

        // 设置格式化参数
        MKFS_PARM opt = {0};
        opt.fmt = FM_FAT32;     // 选择FAT32文件系统格式
        opt.n_fat = 2;          // FAT表的副本数量，通常为2个，用于备份
        opt.align = 1;          // 数据区对齐方式，1表示按扇区对齐
        opt.au_size = 512;      // 分配单元（簇）的大小，单位字节。这里设置为512字节，即一个扇区大小。

        // 调用格式化函数 f_mkfs
        // "1:"      - 盘符
        // &opt      - 格式化参数
        // work      - 工作区内存地址
        // sizeof(work) - 工作区大小
        res_flash = f_mkfs("1:", &opt, work, sizeof(work));

        if (res_flash == FR_OK)
        {
            printf(">>> FLASH已成功格式化为FAT32文件系统。\r\n");
            // 格式化成功后，最好先取消挂载，再重新挂载，以确保文件系统状态被正确加载
            f_mount(NULL, "1:", 1); // 传入NULL表示卸载
            res_flash = f_mount(&fs, "1:", 1);
            if (res_flash != FR_OK)
            {
                printf("！！格式化后重新挂载失败，错误代码: (%d)\r\n", res_flash);
                while(1); // 停机
            }
        }
        else
        {
            printf(">>> 格式化失败！错误代码: (%d)\r\n", res_flash);
            while (1); // 格式化失败，程序停止在这里
        }
    }
    else if (res_flash == FR_OK)
    {
        // 挂载成功，说明FLASH上已经存在一个有效的文件系统
        printf(">>> 文件系统挂载成功，可以进行读写测试。\r\n");
    }
    else
    {
        // 其他错误
        printf("！！外部Flash挂载文件系统失败，错误代码: (%d)\r\n", res_flash);
        printf("！！可能原因：底层SPI FLASH驱动初始化失败或硬件连接问题。\r\n");
        while (1); // 挂载失败，程序停止在这里
    }


    // -----------------------------------------------------------------------------------------------
    // 步骤 2: 文件写入测试
    // -----------------------------------------------------------------------------------------------
    printf("\r\n****** 即将进行文件写入测试 ******\r\n");

    // 打开(或创建)一个名为 "helloworld.txt" 的文件
    // FA_CREATE_ALWAYS: 如果文件已存在，则覆盖；如果不存在，则创建。
    // FA_WRITE:         表示以写入方式打开。
    res_flash = f_open(&fnew, "1:helloworld.txt", FA_CREATE_ALWAYS | FA_WRITE);

    if (res_flash == FR_OK)
    {
        printf(">>> 打开/创建“helloworld.txt”文件成功，准备写入数据。\r\n");

        // 将 fileWriteBuffer 中的内容写入到文件中
        res_flash = f_write(&fnew, fileWriteBuffer, strlen((const char *)fileWriteBuffer), &fnum); // -1 是为了不写入字符串末尾的'\0'

        if (res_flash == FR_OK)
        {
            printf(">>> 文件写入成功，成功写入 %d 个字节。\r\n", fnum);
            printf(">>> 写入的数据为: “%s”\r\n", (const char *)fileWriteBuffer);
        }
        else
        {
            printf("！！文件写入失败，错误代码: (%d)\n", res_flash);
        }

        // 操作完成，必须关闭文件以保存所有更改
        f_close(&fnew);
    }
    else
    {
        printf("！！打开/创建文件失败，错误代码: (%d)\r\n", res_flash);
    }


    // -----------------------------------------------------------------------------------------------
    // 步骤 3: 文件读取测试
    // -----------------------------------------------------------------------------------------------
    printf("\r\n****** 即将进行文件读取测试 ******\r\n");

    // 打开刚刚创建的文件
    // FA_OPEN_EXISTING: 只有当文件存在时才能成功打开。
    // FA_READ:          表示以读取方式打开。
    res_flash = f_open(&fnew, "1:helloworld.txt", FA_OPEN_EXISTING | FA_READ);
    if (res_flash == FR_OK)
    {
        printf(">>> 打开文件成功。\r\n");

        // 从文件中读取数据，并存放到 fileReadBuffer 中
        res_flash = f_read(&fnew, fileReadBuffer, FILE_READ_BUF_SIZE - 1, &fnum); // -1 留出一位给字符串结束符'\0'

        if (res_flash == FR_OK)
        {
            fileReadBuffer[fnum] = '\0'; // 在读取到的数据末尾手动添加字符串结束符
            printf(">>> 文件读取成功，成功读取 %d 个字节。\r\n", fnum);
            printf(">>> 读取到的数据为: “%s”\r\n", fileReadBuffer);
        }
        else
        {
            printf("！！文件读取失败，错误代码: (%d)\n", res_flash);
        }

        // 操作完成，关闭文件
        f_close(&fnew);
    }
    else
    {
        printf("！！打开文件失败，错误代码: (%d)\r\n", res_flash);
    }

    // -----------------------------------------------------------------------------------------------
    // 步骤 4: 卸载文件系统
    // -----------------------------------------------------------------------------------------------
    f_mount(NULL, "1:", 1);
    printf("\r\n****** 文件系统已卸载，测试结束 ******\r\n");

}
